#pragma once
/*
    客户端的服务注册和发现是完全分离的
    1.服务提供者:向服务端发送服务注册请求,判断是否注册成功
    2.服务发现者:需要一个可以进行服务发现的接口,并且将可以提供某种服务的主机信息管理起来
*/
#include "requestor.hpp"
#include <unordered_set>

namespace tmh_rpc
{
    namespace client
    {
        class Provider
        {
        public:
            using ptr = std::shared_ptr<Provider>;

            Provider(const Requestor::ptr &requestor) : _requestor(requestor) {}
            // 向外提供一个服务注册的接口
            // 哪一个主机[host]提供注册哪一个服务[method];同时conn是用来发送消息的(服务上线通知)
            bool registryMethod(const BaseConnection::ptr &conn, const string &method, const Address &host)
            {
                // 1.组织消息请求
                auto msg_req = MessageFactory::create<ServiceRequest>();
                msg_req->setId(UUID::uuid());
                msg_req->setHost(host);
                msg_req->setMethod(method);
                msg_req->setMType(MType::REQ_SERVICE);
                msg_req->setOptype(ServiceOptype::SERVICE_REGISTRY);

                // 2. 发送注册请求,并且获取响应
                BaseMessage::ptr msg_rsp;
                // 同步发送,等待结果
                bool ret = _requestor->send(conn, msg_req, msg_rsp);
                if (ret == false)
                {
                    ELOG("%s 服务注册失败!", method.c_str());
                    return false;
                }

                // 3. 通过输入型参数判断服务是否注册失败
                auto service_rsp = std::dynamic_pointer_cast<ServiceResponse>(msg_rsp);
                if (service_rsp.get() == nullptr)
                {
                    ELOG("响应类型向下转换失败!");
                    return false;
                }
                // 类型转换成功
                if (service_rsp->rcode() != RCode::RCODE_OK)
                {
                    ELOG("服务注册失败,原因:%s", errReason(service_rsp->rcode()).c_str());
                    return false;
                }
                return true; // 服务注册成功
            }

        private:
            // 客户端的请求都是用Requestor模块进行发送的
            Requestor::ptr _requestor;
        };

        // 对主机信息进行管理的模块
        // 一个服务可能会有多台主机节点可以提供:利用RR轮转的策略实现负载均衡
        class MethodHost
        {
        public:
            using ptr = std::shared_ptr<MethodHost>;
            MethodHost() : _idx(0) {}
            MethodHost(const vector<Address> &hosts) : _hosts(hosts.begin(), hosts.end()), _idx(0) {}
            void appendHost(const Address &host)
            {
                // 中途收到服务上线的请求后调用
                // 向管理的map中添加关联信息
                std::unique_lock<std::mutex> lock(_mutex);
                _hosts.push_back(host);
            }
            void removeHost(const Address &host)
            {
                // 中途收到服务下线请求后调用
                std::unique_lock<std::mutex> lock(_mutex);
                for (auto it = _hosts.begin(); it != _hosts.end(); ++it)
                {
                    // 遍历管理关联信息的map,进行删除操作
                    if (*it == host)
                    {
                        _hosts.erase(it);
                        break;
                    }
                }
            }
            // RR轮转:有规律地选择一台主机来进行服务提供
            Address chooseHost()
            {
                std::unique_lock<std::mutex> lock(_mutex);
                size_t pos = (_idx++) % _hosts.size();
                return _hosts[pos];
            }

            bool empty()
            {
                std::unique_lock<std::mutex> lock(_mutex);
                return _hosts.empty();
            }

        private:
            std::mutex _mutex;
            size_t _idx; // 索引表示当前调用到那一台主机了(RR轮转)
            vector<Address> _hosts;
        };

        class Discoverer
        {
        public:
            // 服务下线的回调函数类型:删除客户端的信息,防止内存泄露
            using offlineCallback = std::function<void(const Address &)>;
            using ptr = std::shared_ptr<Discoverer>;

            Discoverer(const Requestor::ptr &requestor, const offlineCallback &cb) : _requestor(requestor), _offline_callback(cb) {}

            bool serviceDiscovery(const BaseConnection::ptr &conn, const string &method, Address &host)
            {
                {
                    // 根据用户请求的服务方法返回主机(host输出型参数):RR轮转;
                    // 如果当前管理的主机没有提供这样的服务-->进行服务发现请求,获取响应;
                    std::unique_lock<std::mutex> lock(_mutex);
                    auto it = _method_hosts.find(method);
                    if (it != _method_hosts.end())
                    {
                        if (it->second->empty() == false)
                        {
                            host = it->second->chooseHost();
                            return true;
                        }
                    }
                }
                // 说明当前管理的主机没有提供这样的服务,进行服务发现请求,获取响应;
                // 1. 组织请求
                auto msg_req = MessageFactory::create<ServiceRequest>();
                msg_req->setId(UUID::uuid());
                msg_req->setMType(MType::REQ_SERVICE);
                msg_req->setMethod(method);
                msg_req->setOptype(ServiceOptype::SERVICE_DISCOVERY);
                // 2. 发送响应
                BaseMessage::ptr msg_rsp;
                // 3. 获取响应
                bool ret = _requestor->send(conn, msg_req, msg_rsp);
                if (ret == false)
                {
                    ELOG("服务发现失败!");
                    return false;
                }
                auto service_rsp = std::dynamic_pointer_cast<ServiceResponse>(msg_rsp);
                if (!service_rsp)
                {
                    ELOG("服务发现失败!响应类型转换失败!");
                    return false;
                }
                // 服务端返回了响应,并且向下转换类型也是成功
                if (service_rsp->rcode() != RCode::RCODE_OK)
                {
                    ELOG("服务发现失败!%s", errReason(service_rsp->rcode()).c_str());
                    return false;
                }

                // 能走到这里,说明服务发现请求得到了响应,并且响应状态码为OK
                std::unique_lock<std::mutex> lock(_mutex);
                // 尝试获取请求到的服务主机
                auto method_host = std::make_shared<MethodHost>(service_rsp->hosts());
                if (method_host->empty())
                {
                    ELOG("%s 服务发现失败! 没有能够提供服务的主机!", method.c_str());
                    return false;
                }

                // 说明发现了可以提供服务的主机:通过输出型参数host返回
                host = method_host->chooseHost();
                // 将发现过的服务和提供主机关联管理起来
                _method_hosts[method] = method_host;
                return true;
            }

            // 这个接口是提供给Dispatcher模块进行服务上线下线请求处理的回调函数
            void onServiceRequest(const BaseConnection::ptr &conn, const ServiceRequest::ptr &msg)
            {
                // 1. 首先判断该请求是上线请求还是下线请求,如果都不是那就不进行处理
                auto optype = msg->opType();
                string method = msg->method();
                std::unique_lock<std::mutex> lock(_mutex);
                // 进行类型判断
                if (optype == ServiceOptype::SERVICE_ONLINE)
                {
                    // 服务上线:找到对应的服务,新增一个主机地址
                    auto it = _method_hosts.find(method);
                    if (it == _method_hosts.end())
                    {
                        // 说明之前没有这样的服务
                        auto method_host = std::make_shared<MethodHost>();
                        method_host->appendHost(msg->host());
                        _method_hosts[method] = method_host;
                    }
                    else
                    {
                        // 说明有这样的服务
                        it->second->appendHost(msg->host());
                    }
                }
                else if (optype == ServiceOptype::SERVICE_OFFLINE)
                {
                    // 服务下线:找到对应的服务,删除对应的主机地址
                    auto it = _method_hosts.find(method);
                    if (it == _method_hosts.end())
                        return;

                    it->second->removeHost(msg->host());
                    // 进行服务下线通知:防止内存泄露
                    _offline_callback(msg->host());
                }
            }

        private:
            // 服务下线的回调函数:删除客户端的信息,防止内存泄露
            offlineCallback _offline_callback;

            std::mutex _mutex;

            // 将服务名称和服务提供者的信息关联管理起来
            std::unordered_map<string, MethodHost::ptr> _method_hosts;

            // 需要通过requestor模块来发送(发现提供者)请求
            Requestor::ptr _requestor;
        };
    }
}